#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
未授权访问检测工具 - 完全修复版
版本: 3.6
功能: 完全修复KeyError、参数错误、显示执行命令、优化性能
作者: 顶级网络安全工程师
"""

import os
import sys
import subprocess
import requests
import urllib3
import json
import time
from urllib.parse import urljoin, urlparse
from bs4 import BeautifulSoup
import re
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed, CancelledError
from datetime import datetime
import socket
import concurrent.futures
import shutil
from pathlib import Path
import signal
import psutil

# 禁用SSL证书验证警告
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

class ProgressTracker:
    """进度跟踪器"""
    def __init__(self, total_steps):
        self.total_steps = total_steps
        self.current_step = 0
        self.start_time = time.time()
        self.lock = threading.Lock()  # 线程安全锁
    
    def update(self, step_name, current=0, total=0):
        """更新进度"""
        with self.lock:
            self.current_step += 1
            elapsed = time.time() - self.start_time
            progress = min(100.0, (self.current_step / self.total_steps) * 100)  # 限制在100%以内
            
            if total > 0:
                print(f"\r[进度: {progress:.1f}%] {step_name} ({current}/{total}) - 耗时: {elapsed:.1f}s", end='', flush=True)
            else:
                print(f"\r[进度: {progress:.1f}%] {step_name} - 耗时: {elapsed:.1f}s", end='', flush=True)

class CommandExecutor:
    """命令执行器 - 显示执行的命令"""
    def __init__(self, report_dir):
        self.report_dir = report_dir
    
    def execute_with_command_display(self, cmd, timeout=300, show_output=True):
        """执行命令并显示执行的命令"""
        print(f"\n[执行命令]: {' '.join(cmd)}")
        
        try:
            process = subprocess.Popen(
                cmd, 
                stdout=subprocess.PIPE, 
                stderr=subprocess.PIPE, 
                universal_newlines=True
            )
            
            # 显示实时输出
            output_lines = []
            start_time = time.time()
            
            while True:
                output = process.stdout.readline()
                if output == '' and process.poll() is not None:
                    break
                if output:
                    output_lines.append(output.strip())
                    if show_output:
                        print(f"\r[输出]: {output.strip()[:60]}...", end='', flush=True)
                
                # 检查超时
                if time.time() - start_time > timeout:
                    process.terminate()
                    try:
                        process.wait(timeout=5)
                    except subprocess.TimeoutExpired:
                        process.kill()
                    return {
                        'success': False,
                        'output': '\n'.join(output_lines),
                        'error': f'命令执行超时 ({timeout}秒)',
                        'returncode': -1
                    }
            
            # 等待进程结束并获取结果
            stdout, stderr = process.communicate()
            return {
                'success': process.returncode == 0,
                'output': stdout,
                'error': stderr,
                'returncode': process.returncode
            }
        except Exception as e:
            return {
                'success': False,
                'output': '',
                'error': str(e),
                'returncode': -1
            }

class UnauthorizedAccessTester:
    def __init__(self, target_url, threads=10):
        """
        初始化未授权访问检测器
        :param target_url: 目标URL
        :param threads: 并发线程数
        """
        self.target_url = target_url
        self.threads = threads
        self.parsed_url = urlparse(target_url)
        self.host = self.parsed_url.hostname
        self.port = self.parsed_url.port or (443 if self.parsed_url.scheme == 'https' else 80)
        self.scheme = self.parsed_url.scheme
        self.session = requests.Session()
        self.session.verify = False
        self.session.headers.update({
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
        })
        # 完全初始化所有结果字段，确保即使在中断情况下也不会出现KeyError
        self.results = {
            'common_unauthorized': [],
            'default_credentials': [],
            'api_unauthorized': [],
            'directory_listings': [],
            'kali_tool_results': {
                'dirb': {'success': False, 'unauthorized_paths': [], 'output': '', 'error': ''},
                'gobuster': {'success': False, 'unauthorized_paths': [], 'output': '', 'error': ''},
                'nikto': {'success': False, 'vulnerabilities': [], 'output': '', 'error': ''},
                'ports': []
            },
            'cors_result': {'cors_headers': {}, 'vulnerabilities': []},
            'vulnerability_summary': {},
            'recommendations': [],
            'scan_timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        }
        self.progress = ProgressTracker(9)  # 总共9个步骤
        self.report_dir = f"unauthorized_access_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
        self.interrupted = False
        self.command_executor = CommandExecutor(self.report_dir)
        self.create_report_directory()
        
        # 设置信号处理
        signal.signal(signal.SIGINT, self.signal_handler)

    def signal_handler(self, signum, frame):
        """信号处理函数"""
        print(f"\n\n[!] 检测被用户中断...")
        self.interrupted = True
        print("[*] 正在保存当前结果...")
        sys.exit(0)

    def create_report_directory(self):
        """创建报告目录"""
        Path(self.report_dir).mkdir(exist_ok=True)
        print(f"[*] 报告目录已创建: {self.report_dir}")

    def test_connectivity(self):
        """测试目标连通性"""
        print(f"[*] 测试目标连通性: {self.target_url}")
        
        try:
            response = self.session.get(self.target_url, timeout=10)
            print(f"[+] 目标可访问，状态码: {response.status_code}")
            return True
        except requests.exceptions.ConnectionError:
            print(f"[!] 无法连接到目标: {self.target_url}")
            print(f"[!] 请检查网络连接或目标主机是否可达")
            return False
        except requests.exceptions.Timeout:
            print(f"[!] 连接目标超时: {self.target_url}")
            return False
        except Exception as e:
            print(f"[!] 连接测试失败: {str(e)}")
            return False

    def check_port_open(self, host, port, timeout=3):
        """检查端口是否开放"""
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(timeout)
            result = sock.connect_ex((host, port))
            sock.close()
            return result == 0
        except:
            return False

    def scan_common_ports(self):
        """扫描常见端口"""
        print(f"\n[*] 扫描常见端口...")
        
        common_ports = [
            21, 22, 23, 25, 53, 80, 110, 111, 135, 139, 143, 443, 445, 993, 995,
            1723, 3306, 3389, 5900, 8080, 8443, 3000, 4000, 5000, 8000, 8081, 8888
        ]
        
        open_ports = []
        
        def scan_port(port):
            if self.interrupted:
                return None
            if self.check_port_open(self.host, port):
                return {
                    'port': port,
                    'status': 'open'
                }
            return None
        
        with ThreadPoolExecutor(max_workers=20) as executor:
            futures = [executor.submit(scan_port, port) for port in common_ports]
            
            completed = 0
            total_ports = len(common_ports)
            
            for future in as_completed(futures):
                if self.interrupted:
                    # 取消剩余任务
                    for f in futures:
                        f.cancel()
                    break
                
                completed += 1
                self.progress.update("端口扫描", completed, total_ports)
                
                result = future.result()
                if result:
                    open_ports.append(result)
                    print(f"\n    [+] 端口 {result['port']} 开放")
        
        print()  # 换行
        return open_ports

    def check_common_unauthorized_endpoints(self):
        """检查常见的未授权访问端点"""
        print(f"\n[*] 检查常见未授权访问端点...")
        
        # 常见的未授权访问路径
        common_endpoints = [
            # 管理后台
            "/admin", "/admin/", "/administrator", "/administrator/", "/admincp", "/admincp/",
            "/admin_panel", "/admin_panel/", "/backend", "/backend/", "/manage", "/manage/",
            
            # 配置文件
            "/config.php", "/config.inc.php", "/config.xml", "/web.config", "/.htaccess",
            "/.git/config", "/.svn/entries", "/WEB-INF/web.xml", "/WEB-INF/classes/config.properties",
            
            # 敏感目录
            "/backup", "/backup/", "/backups", "/backups/", "/temp", "/temp/", "/tmp", "/tmp/",
            "/logs", "/logs/", "/log", "/log/", "/debug", "/debug/", "/test", "/test/",
            
            # API端点
            "/api", "/api/", "/api/v1", "/api/v1/", "/api/v2", "/api/v2/",
            "/api/admin", "/api/admin/", "/api/users", "/api/users/",
            
            # 敏感文件
            "/robots.txt", "/sitemap.xml", "/.env", "/.env.example", "/.gitignore",
            "/docker-compose.yml", "/Dockerfile", "/package.json", "/composer.json",
            
            # 默认路径
            "/phpmyadmin", "/phpmyadmin/", "/mysql", "/mysql/", "/xampp", "/xampp/",
            "/webmin", "/webmin/", "/cpanel", "/cpanel/", "/plesk", "/plesk/",
            
            # 敏感信息泄露
            "/server-status", "/server-info", "/status", "/status/", "/health", "/health/",
            "/metrics", "/metrics/", "/actuator", "/actuator/", "/actuator/health",
            
            # 常见CMS路径
            "/wp-admin", "/wp-admin/", "/wp-login.php", "/wp-config.php",
            "/administrator/index.php", "/joomla/administrator",
            "/drupal/admin", "/drupal/user/login",
            "/magento/admin", "/magento/admin/",
            
            # 开发相关
            "/dev", "/dev/", "/development", "/development/", "/staging", "/staging/",
            "/devops", "/devops/", "/jenkins", "/jenkins/", "/console",
            
            # 数据库相关
            "/db", "/db/", "/database", "/database/", "/data", "/data/",
            "/mongodb", "/redis", "/elasticsearch", "/kibana",
            
            # 云服务相关
            "/aws", "/aws/", "/s3", "/s3/", "/gcp", "/gcp/", "/azure", "/azure/",
            "/.aws/credentials", "/.azure/config", "/gcloud/config",
        ]
        
        unauthorized_endpoints = []
        
        def check_endpoint(endpoint):
            if self.interrupted:
                return None
            try:
                full_url = urljoin(self.target_url, endpoint)
                response = self.session.get(full_url, timeout=10)
                
                # 检查是否可以访问（状态码200或200-300范围）
                if response.status_code in [200, 201, 202, 203, 204, 205, 206]:
                    content = response.text.lower()
                    
                    # 检查是否包含敏感信息或管理界面特征
                    sensitive_indicators = [
                        'admin', 'dashboard', 'login', 'password', 'config', 'database',
                        'user', 'setting', 'management', 'console', 'panel', 'control'
                    ]
                    
                    has_sensitive_info = any(indicator in content for indicator in sensitive_indicators)
                    
                    if has_sensitive_info or len(response.text) > 100:  # 有内容且可能包含敏感信息
                        return {
                            'url': full_url,
                            'status_code': response.status_code,
                            'response_length': len(response.text),
                            'has_sensitive_info': has_sensitive_info,
                            'response_time': response.elapsed.total_seconds()
                        }
                return None
            except Exception as e:
                return None
        
        with ThreadPoolExecutor(max_workers=self.threads) as executor:
            futures = [executor.submit(check_endpoint, endpoint) for endpoint in common_endpoints]
            
            completed = 0
            total_endpoints = len(common_endpoints)
            
            for future in as_completed(futures):
                if self.interrupted:
                    # 取消剩余任务
                    for f in futures:
                        f.cancel()
                    break
                
                completed += 1
                self.progress.update("检查未授权端点", completed, total_endpoints)
                
                result = future.result()
                if result:
                    unauthorized_endpoints.append(result)
                    print(f"\n    [+] 发现未授权访问: {result['url']} (状态码: {result['status_code']})")
        
        print()  # 换行
        # 更新结果
        self.results['common_unauthorized'] = unauthorized_endpoints
        return unauthorized_endpoints

    def run_dirb_scan(self):
        """运行Dirb扫描 - Kali工具"""
        print(f"\n[*] 运行Dirb扫描...")
        
        # 首先检查目标是否可达
        if not self.test_connectivity():
            print(f"[!] 目标不可达，跳过Dirb扫描")
            result = {
                'success': False,
                'output': '目标不可达',
                'unauthorized_paths': [],
                'error': '目标不可达'
            }
            self.results['kali_tool_results']['dirb'] = result
            return result
        
        try:
            # 使用常见的字典文件
            wordlist_path = "/usr/share/wordlists/dirb/common.txt"
            if not os.path.exists(wordlist_path):
                wordlist_path = "/usr/share/seclists/Discovery/Web-Content/common.txt"
            
            if os.path.exists(wordlist_path):
                # 使用更小的字典文件以减少连接错误
                small_wordlist = f"{self.report_dir}/small_dict.txt"
                with open(wordlist_path, 'r') as f:
                    lines = f.readlines()[:300]  # 只取前300行，进一步减少
                
                with open(small_wordlist, 'w') as f:
                    f.writelines(lines)
                
                cmd = [
                    "dirb", 
                    self.target_url, 
                    small_wordlist, 
                    "-o", f"{self.report_dir}/dirb_{self.host}.txt", 
                    "-r",  # 不递归
                    "-S",  # 安静模式
                    "-t", "1",  # 1个请求/秒，减少连接压力
                    "-w"  # 不显示请求
                ]
                
                # 执行命令并显示
                result = self.command_executor.execute_with_command_display(cmd, timeout=180)
                
                # 解析Dirb结果
                unauthorized_paths = []
                if result['success'] and "FOUND:" in result['output']:
                    lines = result['output'].split('\n')
                    for line in lines:
                        if '+ http' in line and '200' in line:
                            # 提取路径
                            match = re.search(r'\+ http.*?(\S+)', line)
                            if match:
                                path = match.group(1)
                                unauthorized_paths.append(path)
                
                final_result = {
                    'success': result['success'],
                    'output': result['output'],
                    'unauthorized_paths': unauthorized_paths,
                    'error': result['error']
                }
                
                # 清理临时字典文件
                if os.path.exists(small_wordlist):
                    os.remove(small_wordlist)
                
                # 更新结果
                self.results['kali_tool_results']['dirb'] = final_result
                return final_result
            else:
                result = {
                    'success': False,
                    'output': '',
                    'unauthorized_paths': [],
                    'error': '字典文件不存在'
                }
                self.results['kali_tool_results']['dirb'] = result
                return result
        except Exception as e:
            result = {
                'success': False,
                'output': '',
                'unauthorized_paths': [],
                'error': str(e)
            }
            self.results['kali_tool_results']['dirb'] = result
            return result

    def run_gobuster_scan(self):
        """运行Gobuster扫描 - Kali工具"""
        print(f"\n[*] 运行Gobuster扫描...")
        
        # 首先检查目标是否可达
        if not self.test_connectivity():
            print(f"[!] 目标不可达，跳过Gobuster扫描")
            result = {
                'success': False,
                'output': '目标不可达',
                'unauthorized_paths': [],
                'error': '目标不可达'
            }
            self.results['kali_tool_results']['gobuster'] = result
            return result
        
        try:
            # 使用常见的字典文件
            wordlist_path = "/usr/share/wordlists/dirb/common.txt"
            if not os.path.exists(wordlist_path):
                wordlist_path = "/usr/share/seclists/Discovery/Web-Content/common.txt"
            
            if os.path.exists(wordlist_path):
                # 使用更小的字典文件以减少连接错误
                small_wordlist = f"{self.report_dir}/small_gobuster_dict.txt"
                with open(wordlist_path, 'r') as f:
                    lines = f.readlines()[:200]  # 只取前200行，进一步减少
                
                with open(small_wordlist, 'w') as f:
                    f.writelines(lines)
                
                # 修复Gobuster参数 - 移除冲突的参数
                cmd = [
                    "gobuster", "dir",
                    "-u", self.target_url,
                    "-w", small_wordlist,
                    "-t", "2",  # 进一步减少并发数
                    "-x", "php,html,js,txt,json,xml,asp,aspx,jsp",
                    "-o", f"{self.report_dir}/gobuster_{self.host}.txt",
                    "-r",  # 不跟随重定向
                    "-q",  # 安静模式
                    "-e",  # 不显示错误
                    "-s", "200,204,301,302,307,403,500",  # 只显示这些状态码
                    "-exclude-length", "404"  # 正确的排除参数
                ]
                
                # 执行命令并显示
                result = self.command_executor.execute_with_command_display(cmd, timeout=180)
                
                # 解析Gobuster结果
                unauthorized_paths = []
                if result['success']:
                    lines = result['output'].split('\n')
                    for line in lines:
                        if 'Status:' in line and '200' in line:
                            # 提取路径
                            parts = line.split()
                            if len(parts) > 0:
                                path = parts[0]
                                unauthorized_paths.append(path)
                
                final_result = {
                    'success': result['success'],
                    'output': result['output'],
                    'unauthorized_paths': unauthorized_paths,
                    'error': result['error']
                }
                
                # 清理临时字典文件
                if os.path.exists(small_wordlist):
                    os.remove(small_wordlist)
                
                # 更新结果
                self.results['kali_tool_results']['gobuster'] = final_result
                return final_result
            else:
                result = {
                    'success': False,
                    'output': '',
                    'unauthorized_paths': [],
                    'error': '字典文件不存在'
                }
                self.results['kali_tool_results']['gobuster'] = result
                return result
        except Exception as e:
            result = {
                'success': False,
                'output': '',
                'unauthorized_paths': [],
                'error': str(e)
            }
            self.results['kali_tool_results']['gobuster'] = result
            return result

    def run_nikto_scan(self):
        """运行Nikto扫描 - Kali工具"""
        print(f"\n[*] 运行Nikto扫描...")
        
        # 首先检查目标是否可达
        if not self.test_connectivity():
            print(f"[!] 目标不可达，跳过Nikto扫描")
            result = {
                'success': False,
                'output': '目标不可达',
                'vulnerabilities': [],
                'error': '目标不可达'
            }
            self.results['kali_tool_results']['nikto'] = result
            return result
        
        try:
            # 修复Nikto参数 - 减少错误限制，添加连接重试
            cmd = [
                "nikto",
                "-h", self.host,
                "-p", str(self.port),
                "-output", f"{self.report_dir}/nikto_{self.host}.txt",
                "-C", "1,2,3,4,5,6,7,8,9",  # 选择性测试
                "-ask", "no",  # 不询问
                "-timeout", "10",  # 设置超时
                "-Tuning", "1,2,3,4,5,6,7,8,9",  # 选择性测试
                "-maxtime", "180",  # 最大执行时间3分钟
                "-maxerrors", "5",  # 降低错误限制
                "-Pause", "0.5",  # 请求间隔
                "-useragent", "Mozilla/5.0 (compatible; Security Scanner)"
            ]
            
            # 执行命令并显示，设置较短超时
            result = self.command_executor.execute_with_command_display(cmd, timeout=300, show_output=True)
            
            # 解析Nikto结果
            vulnerabilities = []
            if result['success']:
                lines = result['output'].split('\n')
                for line in lines:
                    if 'VULNERABLE' in line.upper() or 'UNAUTHORIZED' in line.upper() or 'X-' in line:
                        vulnerabilities.append(line.strip())
            
            final_result = {
                'success': result['success'],
                'output': result['output'],
                'vulnerabilities': vulnerabilities,
                'error': result['error']
            }
            
            # 更新结果
            self.results['kali_tool_results']['nikto'] = final_result
            return final_result
        except Exception as e:
            result = {
                'success': False,
                'output': '',
                'vulnerabilities': [],
                'error': str(e)
            }
            self.results['kali_tool_results']['nikto'] = result
            return result

    def check_default_credentials(self):
        """检查默认凭据"""
        print(f"\n[*] 检查默认凭据...")
        
        # 常见的默认凭据组合
        default_credentials = [
            # 常见Web应用
            ("admin", "admin"), ("admin", "password"), ("admin", "123456"),
            ("root", "root"), ("root", "password"), ("root", "toor"),
            ("user", "user"), ("guest", "guest"), ("test", "test"),
            
            # 数据库默认凭据
            ("sa", ""), ("sa", "sa"), ("postgres", "postgres"),
            ("mysql", ""), ("admin", "mysql"), ("root", ""),
            
            # CMS默认凭据
            ("admin", "admin123"), ("administrator", "admin"),
            ("webmaster", "webmaster"), ("manager", "manager"),
            
            # 常见弱口令
            ("admin", "123456"), ("admin", "admin"), ("admin", "password123"),
            ("user", "password"), ("guest", "password"), ("test", "123456"),
        ]
        
        vulnerable_endpoints = []
        
        # 尝试访问需要认证的端点
        auth_required_endpoints = [
            "/admin", "/admin/", "/login", "/login/", "/dashboard", "/dashboard/",
            "/manage", "/manage/", "/backend", "/backend/", "/api/admin", "/api/admin/"
        ]
        
        total_tests = len(auth_required_endpoints) * len(default_credentials[:10])
        completed_tests = 0
        
        for endpoint in auth_required_endpoints:
            if self.interrupted:
                break
            full_url = urljoin(self.target_url, endpoint)
            
            for username, password in default_credentials[:10]:  # 只测试前10个
                if self.interrupted:
                    break
                completed_tests += 1
                self.progress.update("检查默认凭据", completed_tests, total_tests)
                
                try:
                    # 尝试使用基本认证
                    response = self.session.get(full_url, 
                                              auth=(username, password), 
                                              timeout=10)
                    
                    if response.status_code in [200, 201, 202, 203, 204, 205, 206]:
                        vulnerable_endpoints.append({
                            'url': full_url,
                            'username': username,
                            'password': password,
                            'status_code': response.status_code
                        })
                        print(f"\n    [!] 发现默认凭据: {username}:{password} -> {full_url}")
                        break  # 找到一个就继续下一个端点
                except Exception:
                    continue
        
        print()  # 换行
        # 更新结果
        self.results['default_credentials'] = vulnerable_endpoints
        return vulnerable_endpoints

    def check_api_endpoints(self):
        """检查API端点未授权访问"""
        print(f"\n[*] 检查API端点未授权访问...")
        
        api_endpoints = [
            "/api/users", "/api/users/", "/api/admin", "/api/admin/",
            "/api/v1/users", "/api/v1/admin", "/api/v2/users", "/api/v2/admin",
            "/users", "/users/", "/admin", "/admin/", "/api/auth", "/api/auth/",
            "/api/session", "/api/session/", "/api/token", "/api/token/",
            "/api/config", "/api/config/", "/api/settings", "/api/settings/"
        ]
        
        unauthorized_apis = []
        
        total_tests = len(api_endpoints) * 2  # GET和POST
        completed_tests = 0
        
        for endpoint in api_endpoints:
            if self.interrupted:
                break
            full_url = urljoin(self.target_url, endpoint)
            
            try:
                # 尝试GET请求
                completed_tests += 1
                self.progress.update("检查API端点", completed_tests, total_tests)
                
                response = self.session.get(full_url, timeout=10)
                
                if response.status_code == 200:
                    content = response.text.lower()
                    
                    # 检查是否返回敏感数据
                    sensitive_data_indicators = [
                        'token', 'password', 'secret', 'key', 'email', 'phone',
                        'address', 'user', 'admin', 'role', 'permission'
                    ]
                    
                    has_sensitive_data = any(indicator in content for indicator in sensitive_data_indicators)
                    
                    if has_sensitive_data or len(content) > 50:  # 有数据且可能包含敏感信息
                        unauthorized_apis.append({
                            'url': full_url,
                            'method': 'GET',
                            'status_code': response.status_code,
                            'response_length': len(content),
                            'has_sensitive_data': has_sensitive_data
                        })
                        print(f"\n    [+] API未授权访问: {full_url} (GET)")
                
                # 尝试POST请求（空数据）
                completed_tests += 1
                self.progress.update("检查API端点", completed_tests, total_tests)
                
                response_post = self.session.post(full_url, json={}, timeout=10)
                
                if response_post.status_code == 200:
                    content_post = response_post.text.lower()
                    has_sensitive_data_post = any(indicator in content_post for indicator in sensitive_data_indicators)
                    
                    if has_sensitive_data_post or len(content_post) > 50:
                        unauthorized_apis.append({
                            'url': full_url,
                            'method': 'POST',
                            'status_code': response_post.status_code,
                            'response_length': len(content_post),
                            'has_sensitive_data': has_sensitive_data_post
                        })
                        print(f"\n    [+] API未授权访问: {full_url} (POST)")
                        
            except Exception as e:
                continue
        
        print()  # 换行
        # 更新结果
        self.results['api_unauthorized'] = unauthorized_apis
        return unauthorized_apis

    def check_directory_listing(self):
        """检查目录列表"""
        print(f"\n[*] 检查目录列表...")
        
        # 常见的目录
        directories = [
            "/backup", "/backup/", "/temp", "/temp/", "/tmp", "/tmp/",
            "/logs", "/logs/", "/log", "/log/", "/data", "/data/",
            "/files", "/files/", "/uploads", "/uploads/", "/images", "/images/",
            "/docs", "/docs/", "/documents", "/documents/", "/config", "/config/"
        ]
        
        directory_listings = []
        
        total_dirs = len(directories)
        
        for i, directory in enumerate(directories, 1):
            if self.interrupted:
                break
            self.progress.update("检查目录列表", i, total_dirs)
            
            full_url = urljoin(self.target_url, directory)
            
            try:
                response = self.session.get(full_url, timeout=10)
                
                if response.status_code == 200:
                    content = response.text.lower()
                    
                    # 检查是否显示目录列表
                    directory_indicators = [
                        'index of', 'directory listing', 'parent directory',
                        'name', 'last modified', 'size', 'type', 'folder'
                    ]
                    
                    has_directory_listing = any(indicator in content for indicator in directory_indicators)
                    
                    if has_directory_listing:
                        directory_listings.append({
                            'url': full_url,
                            'status_code': response.status_code,
                            'response_length': len(content)
                        })
                        print(f"\n    [+] 发现目录列表: {full_url}")
                        
            except Exception:
                continue
        
        print()  # 换行
        # 更新结果
        self.results['directory_listings'] = directory_listings
        return directory_listings

    def check_cors_misconfiguration(self):
        """检查CORS配置错误"""
        print(f"\n[*] 检查CORS配置错误...")
        
        try:
            # 尝试使用不同的Origin头
            headers = {
                'Origin': 'https://evil.com',
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
            }
            
            response = self.session.get(self.target_url, headers=headers, timeout=10)
            
            cors_headers = {
                'access-control-allow-origin': response.headers.get('Access-Control-Allow-Origin'),
                'access-control-allow-credentials': response.headers.get('Access-Control-Allow-Credentials'),
                'access-control-allow-methods': response.headers.get('Access-Control-Allow-Methods'),
                'access-control-allow-headers': response.headers.get('Access-Control-Allow-Headers')
            }
            
            vulnerabilities = []
            
            if cors_headers['access-control-allow-origin'] == '*' and not cors_headers.get('access-control-allow-credentials', '').lower() == 'true':
                vulnerabilities.append("CORS配置过于宽松 - 允许所有域访问")
            elif cors_headers['access-control-allow-origin'] == 'https://evil.com':
                vulnerabilities.append("CORS反射漏洞 - 可反射恶意域")
            
            result = {
                'cors_headers': cors_headers,
                'vulnerabilities': vulnerabilities
            }
            
            # 更新结果
            self.results['cors_result'] = result
            return result
        except Exception as e:
            result = {
                'cors_headers': {},
                'vulnerabilities': [],
                'error': str(e)
            }
            self.results['cors_result'] = result
            return result

    def run_comprehensive_test(self):
        """运行综合测试"""
        print(f"\n[*] 开始综合未授权访问测试: {self.target_url}")
        print("="*60)
        
        # 首先测试连通性
        if not self.test_connectivity():
            print(f"[!] 目标不可达，无法进行扫描")
            return self.results
        
        try:
            # 1. 检查常见未授权端点
            common_unauthorized = self.check_common_unauthorized_endpoints()
            if not self.interrupted:
                self.progress.update("检查常见未授权端点")
        
            # 2. 运行Dirb扫描
            if not self.interrupted:
                dirb_result = self.run_dirb_scan()
                self.progress.update("运行Dirb扫描")
        
            # 3. 运行Gobuster扫描
            if not self.interrupted:
                gobuster_result = self.run_gobuster_scan()
                self.progress.update("运行Gobuster扫描")
        
            # 4. 运行Nikto扫描
            if not self.interrupted:
                nikto_result = self.run_nikto_scan()
                self.progress.update("运行Nikto扫描")
        
            # 5. 检查默认凭据
            if not self.interrupted:
                default_creds = self.check_default_credentials()
                self.progress.update("检查默认凭据")
        
            # 6. 检查API端点
            if not self.interrupted:
                api_unauthorized = self.check_api_endpoints()
                self.progress.update("检查API端点")
        
            # 7. 检查目录列表
            if not self.interrupted:
                dir_listings = self.check_directory_listing()
                self.progress.update("检查目录列表")
        
            # 8. 运行端口扫描
            if not self.interrupted:
                port_scan_result = self.scan_common_ports()
                self.results['kali_tool_results']['ports'] = port_scan_result
                self.progress.update("扫描开放端口")
        
            # 9. 检查CORS配置
            if not self.interrupted:
                cors_result = self.check_cors_misconfiguration()
                self.progress.update("检查CORS配置")
        
            # 汇总结果
            if not self.interrupted:
                self.results['vulnerability_summary'] = self.generate_summary()
                self.results['recommendations'] = self.generate_recommendations()
                self.results['scan_timestamp'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        
                return self.results
            else:
                print("\n[!] 扫描被中断，返回部分结果...")
                # 即使中断也要生成摘要
                self.results['vulnerability_summary'] = self.generate_summary()
                self.results['recommendations'] = self.generate_recommendations()
                self.results['scan_timestamp'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
                return self.results
                
        except KeyboardInterrupt:
            print("\n[!] 检测被用户中断...")
            # 即使中断也要生成摘要
            self.results['vulnerability_summary'] = self.generate_summary()
            self.results['recommendations'] = self.generate_recommendations()
            self.results['scan_timestamp'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            return self.results

    def generate_summary(self):
        """生成漏洞摘要 - 使用安全的字典访问"""
        try:
            summary = {
                'total_unauthorized_endpoints': len(self.results.get('common_unauthorized', [])),
                'default_credential_vulnerabilities': len(self.results.get('default_credentials', [])),
                'api_unauthorized_access': len(self.results.get('api_unauthorized', [])),
                'directory_listings': len(self.results.get('directory_listings', [])),
                'dirb_vulnerabilities': len(self.results.get('kali_tool_results', {}).get('dirb', {}).get('unauthorized_paths', [])),
                'gobuster_vulnerabilities': len(self.results.get('kali_tool_results', {}).get('gobuster', {}).get('unauthorized_paths', [])),
                'nikto_vulnerabilities': len(self.results.get('kali_tool_results', {}).get('nikto', {}).get('vulnerabilities', [])),
                'cors_vulnerabilities': len(self.results.get('cors_result', {}).get('vulnerabilities', [])),
                'open_ports': len(self.results.get('kali_tool_results', {}).get('ports', []))
            }
        except Exception as e:
            # 如果出现任何错误，返回默认摘要
            summary = {
                'total_unauthorized_endpoints': 0,
                'default_credential_vulnerabilities': 0,
                'api_unauthorized_access': 0,
                'directory_listings': 0,
                'dirb_vulnerabilities': 0,
                'gobuster_vulnerabilities': 0,
                'nikto_vulnerabilities': 0,
                'cors_vulnerabilities': 0,
                'open_ports': 0
            }
        
        return summary

    def generate_recommendations(self):
        """生成安全建议"""
        recommendations = [
            "1. 实施严格的访问控制机制",
            "2. 移除或保护敏感目录和文件",
            "3. 修复默认凭据问题",
            "4. 实施API访问控制和认证",
            "5. 禁用目录列表功能",
            "6. 配置安全的CORS策略",
            "7. 定期进行安全扫描和渗透测试",
            "8. 实施最小权限原则",
            "9. 加强输入验证和输出编码",
            "10. 定期更新和修补系统"
        ]
        
        return recommendations

    def save_detailed_report(self):
        """保存详细报告到文件"""
        if not os.path.exists(self.report_dir):
            Path(self.report_dir).mkdir(exist_ok=True)
        
        report_file = f"{self.report_dir}/unauthorized_access_report.json"
        
        # 保存完整的JSON报告
        with open(report_file, 'w', encoding='utf-8') as f:
            json.dump(self.results, f, indent=2, ensure_ascii=False)
        
        # 生成人类可读的报告
        readable_report = f"{self.report_dir}/unauthorized_access_report.txt"
        with open(readable_report, 'w', encoding='utf-8') as f:
            f.write("="*60 + "\n")
            f.write("未授权访问检测详细报告\n")
            f.write("="*60 + "\n")
            f.write(f"目标URL: {self.target_url}\n")
            f.write(f"检测时间: {self.results.get('scan_timestamp', datetime.now().strftime('%Y-%m-%d %H:%M:%S'))}\n")
            f.write(f"报告目录: {self.report_dir}\n")
            f.write(f"中断状态: {'是' if self.interrupted else '否'}\n")
            f.write("\n")
            
            # 摘要信息
            summary = self.results.get('vulnerability_summary', {})
            f.write("漏洞摘要:\n")
            f.write("-" * 40 + "\n")
            f.write(f"未授权访问端点: {summary.get('total_unauthorized_endpoints', 0)}\n")
            f.write(f"默认凭据漏洞: {summary.get('default_credential_vulnerabilities', 0)}\n")
            f.write(f"API未授权访问: {summary.get('api_unauthorized_access', 0)}\n")
            f.write(f"目录列表漏洞: {summary.get('directory_listings', 0)}\n")
            f.write(f"CORS配置错误: {summary.get('cors_vulnerabilities', 0)}\n")
            f.write(f"开放端口: {summary.get('open_ports', 0)}\n")
            f.write("\n")
            
            # 详细结果
            f.write("详细检测结果:\n")
            f.write("-" * 40 + "\n")
            
            # 常见未授权端点
            common_unauth = self.results.get('common_unauthorized', [])
            if common_unauth:
                f.write("\n未授权访问端点:\n")
                for endpoint in common_unauth:
                    f.write(f"  • {endpoint['url']} (状态码: {endpoint['status_code']}, 长度: {endpoint['response_length']})\n")
            
            # 默认凭据
            default_creds = self.results.get('default_credentials', [])
            if default_creds:
                f.write("\n默认凭据漏洞:\n")
                for cred in default_creds:
                    f.write(f"  • {cred['url']} - {cred['username']}:{cred['password']}\n")
            
            # API未授权访问
            api_unauth = self.results.get('api_unauthorized', [])
            if api_unauth:
                f.write("\nAPI未授权访问:\n")
                for api in api_unauth:
                    f.write(f"  • {api['url']} ({api['method']}) - 长度: {api['response_length']}\n")
            
            # 目录列表
            dir_listings = self.results.get('directory_listings', [])
            if dir_listings:
                f.write("\n目录列表漏洞:\n")
                for dir_list in dir_listings:
                    f.write(f"  • {dir_list['url']} - 长度: {dir_list['response_length']}\n")
            
            # CORS配置
            cors_vulns = self.results.get('cors_result', {}).get('vulnerabilities', [])
            if cors_vulns:
                f.write("\nCORS配置错误:\n")
                for vuln in cors_vulns:
                    f.write(f"  • {vuln}\n")
            
            # 开放端口
            open_ports = self.results.get('kali_tool_results', {}).get('ports', [])
            if open_ports:
                f.write(f"\n开放端口 ({len(open_ports)} 个):\n")
                for port_info in open_ports:
                    f.write(f"  • 端口 {port_info['port']}\n")
            
            # Kali工具结果
            f.write("\nKali工具扫描结果:\n")
            f.write("-" * 40 + "\n")
            
            # Dirb结果
            dirb = self.results.get('kali_tool_results', {}).get('dirb', {})
            if dirb.get('success') and dirb.get('unauthorized_paths'):
                f.write(f"\nDirb发现 {len(dirb['unauthorized_paths'])} 个路径:\n")
                for path in dirb['unauthorized_paths'][:10]:  # 显示前10个
                    f.write(f"  • {path}\n")
            elif not dirb.get('success'):
                f.write(f"\nDirb扫描失败: {dirb.get('error', '未知错误')}\n")
            
            # Gobuster结果
            gobuster = self.results.get('kali_tool_results', {}).get('gobuster', {})
            if gobuster.get('success') and gobuster.get('unauthorized_paths'):
                f.write(f"\nGobuster发现 {len(gobuster['unauthorized_paths'])} 个路径:\n")
                for path in gobuster['unauthorized_paths'][:10]:  # 显示前10个
                    f.write(f"  • {path}\n")
            elif not gobuster.get('success'):
                f.write(f"\nGobuster扫描失败: {gobuster.get('error', '未知错误')}\n")
            
            # Nikto结果
            nikto = self.results.get('kali_tool_results', {}).get('nikto', {})
            if nikto.get('success') and nikto.get('vulnerabilities'):
                f.write(f"\nNikto发现 {len(nikto['vulnerabilities'])} 个漏洞:\n")
                for vuln in nikto['vulnerabilities'][:5]:  # 显示前5个
                    f.write(f"  • {vuln}\n")
            elif not nikto.get('success'):
                f.write(f"\nNikto扫描失败: {nikto.get('error', '未知错误')}\n")
            
            # 安全建议
            f.write("\n安全建议:\n")
            f.write("-" * 40 + "\n")
            for rec in self.results.get('recommendations', self.generate_recommendations()):
                f.write(f"• {rec}\n")
            
            f.write("="*60 + "\n")
        
        print(f"\n[+] 详细报告已保存至: {self.report_dir}/")
        print(f"    - JSON格式: unauthorized_access_report.json")
        print(f"    - 文本格式: unauthorized_access_report.txt")
        print(f"    - 工具输出: dirb_{self.host}.txt, gobuster_{self.host}.txt, nikto_{self.host}.txt")

    def print_summary_report(self):
        """打印摘要报告"""
        print("\n" + "="*60)
        print("未授权访问检测摘要报告")
        print("="*60)
        print(f"目标URL: {self.target_url}")
        print(f"检测时间: {self.results.get('scan_timestamp', datetime.now().strftime('%Y-%m-%d %H:%M:%S'))}")
        print(f"报告目录: {self.report_dir}")
        print(f"中断状态: {'是' if self.interrupted else '否'}")
        print()
        
        # 摘要信息
        summary = self.results.get('vulnerability_summary', {})
        print("漏洞摘要:")
        print("-" * 40)
        print(f"未授权访问端点: {summary.get('total_unauthorized_endpoints', 0)}")
        print(f"默认凭据漏洞: {summary.get('default_credential_vulnerabilities', 0)}")
        print(f"API未授权访问: {summary.get('api_unauthorized_access', 0)}")
        print(f"目录列表漏洞: {summary.get('directory_listings', 0)}")
        print(f"CORS配置错误: {summary.get('cors_vulnerabilities', 0)}")
        print(f"开放端口: {summary.get('open_ports', 0)}")
        print()
        
        # 总体评估
        total_vulns = sum([
            summary.get('total_unauthorized_endpoints', 0),
            summary.get('default_credential_vulnerabilities', 0),
            summary.get('api_unauthorized_access', 0),
            summary.get('directory_listings', 0),
            summary.get('cors_vulnerabilities', 0)
        ])
        
        if total_vulns == 0:
            risk_level = "✅ 低风险 - 未发现明显漏洞"
        elif total_vulns <= 3:
            risk_level = "⚠️  中等风险 - 存在少量漏洞"
        elif total_vulns <= 6:
            risk_level = "🚨 高风险 - 存在多个漏洞"
        else:
            risk_level = "🔥 严重风险 - 存在大量漏洞"
        
        print(f"总体风险评估: {risk_level}")
        print(f"总发现漏洞数: {total_vulns}")
        
        if self.interrupted:
            print("⚠️  检测被中断 - 结果可能不完整")
        
        print("="*60)

def get_optimal_threads():
    """获取最优并发数"""
    try:
        import os
        import psutil
        
        # 获取系统信息
        cpu_count = os.cpu_count()
        memory_gb = psutil.virtual_memory().total / (1024**3)
        
        # 基于CPU和内存计算最优并发数
        optimal = min(
            cpu_count * 2,  # CPU核心数的2倍
            int(memory_gb * 20),  # 内存GB数 * 20
            30  # 上限30
        )
        
        return max(5, optimal)  # 至少5个，最多optimal个
    except:
        return 10  # 默认10个

def main():
    """主函数"""
    print("""
    ===========================================
    |    未授权访问检测工具 v3.6             |
    |  完全修复版 - 解决所有KeyError问题       |
    ===========================================
    """)
    
    print("⚠️  警告: 此工具仅用于授权安全测试!")
    print("请确保您有权限测试目标系统\n")
    
    target_url = input("[+] 请输入目标URL (如: https://example.com): ").strip()
    
    if not target_url.startswith(('http://', 'https://')):
        target_url = 'https://' + target_url
    
    # 自动计算最优并发数
    try:
        optimal_threads = get_optimal_threads()
        
        print(f"[*] 检测到CPU核心数: {os.cpu_count()}")
        print(f"[*] 检测到内存大小: {psutil.virtual_memory().total / (1024**3):.1f}GB")
        print(f"[*] 推荐并发线程数: {optimal_threads}")
        
        threads_input = input(f"[+] 请输入并发线程数 (默认{optimal_threads}, 最大50): ").strip()
        if threads_input:
            threads = min(int(threads_input), 50)
        else:
            threads = optimal_threads
    except:
        # 如果无法获取系统信息，使用默认值
        threads = 10
    
    print(f"\n[*] 开始检测...")
    print(f"[*] 目标: {target_url}")
    print(f"[*] 线程数: {threads}")
    print(f"[*] 按 Ctrl+C 可随时中断检测")
    
    # 创建检测器并运行测试
    tester = UnauthorizedAccessTester(target_url, threads)
    results = tester.run_comprehensive_test()
    
    # 打印摘要报告
    tester.print_summary_report()
    
    # 保存详细报告
    tester.save_detailed_report()
    
    print(f"\n[*] 检测完成! 详细报告已生成在: {tester.report_dir}/")

if __name__ == "__main__":
    main()